#!/usr/bin/env python
#
# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import cgi
import os
import random

import gdata.service
import gdata.urlfetch
# Configure gdata python client to use app engine URL Fetch
gdata.service.http_request_handler = gdata.urlfetch

from gdata.spreadsheet.service import SpreadsheetsService

from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.ext.webapp.util import run_wsgi_app


class UserConfig(db.Model):
  """Store configuration for one user"""
  user = db.UserProperty(required=True)
  auth_token = db.StringProperty()
  auth_token_valid = db.BooleanProperty()
  spreadsheet_key = db.StringProperty()
  worksheet_key = db.StringProperty()

class CardflasherClient(SpreadsheetsService):
  """Handle communication with the spreadsheet gdata service"""

  def  __init__(self, user_config):
    SpreadsheetsService.__init__(self)
    self.source = "markstahl-cardflasher-0.9"
    if user_config.auth_token == "default":
      # Default config: use Client Login, talk to a hardwired spreadsheet
      self.email = 'markstahl.api@gmail.com'
      self.password = 'testtest'
      self.ProgrammaticLogin()
      self.spreadsheet_key = 'pFTNU5W-U0barlRKCdCAaKg'
      self.worksheet_key = 'od6'
    else:
      # Reuse the existing authsub token and configured spreadsheet
      self.SetAuthSubToken(user_config.auth_token)
      self.spreadsheet_key = user_config.spreadsheet_key
      self.worksheet_key = user_config.worksheet_key

  def getAllEntries(self):
    """ Get all cards in the card stack"""
    feed = self.GetListFeed(self.spreadsheet_key, self.worksheet_key)
    return feed.entry

  def getEntry(self, url):
    """Get one card in the card stack"""
    return self.Get(url, converter=gdata.spreadsheet.SpreadsheetsListFromString)

  def updateEntry(self, url, entry):
    return self.Put(entry, url, converter=gdata.spreadsheet.SpreadsheetsListFromString)

  def insertEntry(self, key_dict):
    return self.InsertRow(key_dict, self.spreadsheet_key, self.worksheet_key)

  def deleteEntry(self, edit_url):
    return self.Delete(edit_url)


class Card:
  """The data from one flashcard"""

  def initFromEntry(self, entry, client):
    """Initialize card from gdata entry"""
    self.front = entry.custom["front"].text
    self.back = entry.custom["back"].text
    score = entry.custom["score"].text
    if score == None:
      self.score = 0
    else:
      self.score = int(score)
    self.edit_url = ""
    for link in entry.link:
      if link.rel == 'edit':
        self.edit_url = link.href
        break
    self.client = client
    return self

  def initFromEditUrl(self, edit_url, client):
    """Initialize card from the gdata edit url"""
    entry = client.getEntry(edit_url)
    return self.initFromEntry(entry, client)

  def Create(self, front, back, client):
    """insert a new row into the spreadsheet"""
    key_dict = {}
    key_dict["front"] = str(front)
    key_dict["back"] = str(back)
    key_dict["score"] = str(0)
    entry = client.insertEntry(key_dict)
    self.initFromEntry(entry, client)
    return self

  def Delete(self):
    self.client.deleteEntry(self.edit_url)

  def UpdateScore(self, new_score):
    """Modify the score of a card"""
    entry = self.client.getEntry(self.edit_url)
    entry.custom["score"].text = str(new_score)
    self.client.updateEntry(self.edit_url, entry)


class BaseHandler(webapp.RequestHandler):
  def getUserConfig(self):
    # Is the user logged in?
    if not users.get_current_user():
      self.redirect(users.create_login_url('/'))
      return None
    # Is this a returning user?
    user_config = UserConfig.gql("WHERE user = :1", users.get_current_user()).get()
    if not user_config:
      # Create a new user account
      user_config = UserConfig(user=users.get_current_user())
      user_config.auth_token = 'default'
      user_config.put();
    return user_config


class RootHandler(BaseHandler):
  def get(self):
    # Check that user is properly configured
    user_config = self.getUserConfig()
    if user_config == None:
      return
    # jump right into a question
    self.redirect('/question')


class ShowQuestion(BaseHandler):
  def get(self):
    # Check that user is properly configured
    user_config = self.getUserConfig()
    if user_config == None:
      return
    try:
      # Get a client that talks to the configured spreadsheet
      client = CardflasherClient(user_config)
      # Build the stack of cards from Spreadsheet data
      entries = client.getAllEntries()
      # Convert entries to card stack
      cards = []
      for entry in entries:
        cards.append(Card().initFromEntry(entry, client))
      # Choose card at random
      index = random.randint(0, len(cards)-1)
      card = cards[index]
      # show the question page
      template_dict = {
          'name'           : users.get_current_user().email(),
          'logout_url'     : users.create_logout_url('/'),
          'card'           : card,
          }
      path = os.path.join(os.path.dirname(__file__), 'templates/question.html');
      self.response.out.write(template.render(path, template_dict))
    except gdata.service.RequestError, request_error:
      user_config.auth_token_valid = False;
      user_config.put()
      self.redirect('/config')


class ShowAnswer(BaseHandler):
  def post(self):
    # Check that user is properly configured
    user_config = self.getUserConfig()
    if user_config == None:
      return
    try:
      # get the card data from the spreadsheet using gdata
      client = CardflasherClient(user_config)
      card = Card().initFromEditUrl(self.request.get('edit_url'), client)
      # show the answer page
      template_dict = {
          'name'           : users.get_current_user().email(),
          'logout_url'     : users.create_logout_url('/'),
          'card'           : card,
          }
      path = os.path.join(os.path.dirname(__file__), 'templates/answer.html');
      self.response.out.write(template.render(path, template_dict))
    except gdata.service.RequestError, request_error:
      user_config.auth_token_valid = False;
      user_config.put()
      self.redirect('/config')


class UpdateCard(BaseHandler):
  def post(self):
    # Check that user is properly configured
    user_config = self.getUserConfig()
    if user_config == None:
      return
    try:
      # get the card data from the spreadsheet using gdata
      client = CardflasherClient(user_config)
      card = Card().initFromEditUrl(self.request.get('edit_url'), client)
      # increment the score on this card
      if self.request.get('correct') == 'yes':
        card.UpdateScore(card.score+1)
      # goto another card
      self.redirect('/question')
    except gdata.service.RequestError, request_error:
      user_config.auth_token_valid = False;
      user_config.put()
      self.redirect('/config')


class ListCards(BaseHandler):
  def get(self):
    # Check that user is properly configured
    user_config = self.getUserConfig()
    if user_config == None:
      return
    try:
      client = CardflasherClient(user_config)
      entries = client.getAllEntries()
      cards = []
      for entry in entries:
        cards.append(Card().initFromEntry(entry, client))
      # url for editing the spreadsheet in google docs
      spreadsheet_url = "http://spreadsheets.google.com/ccc?key=%s" % (
          user_config.spreadsheet_key)
      # show the list page
      template_dict = {
          'name'           : users.get_current_user().email(),
          'logout_url'     : users.create_logout_url('/'),
          'cards'          : cards,
          'spreadsheet_url': spreadsheet_url
          }
      path = os.path.join(os.path.dirname(__file__), 'templates/list.html');
      self.response.out.write(template.render(path, template_dict))
    except gdata.service.RequestError, request_error:
      user_config.auth_token_valid = False;
      user_config.put()
      self.redirect('/config')


class InsertCard(BaseHandler):
  def post(self):
    # Check that user is properly configured
    user_config = self.getUserConfig()
    if user_config == None:
      return
    try:
      # add card to spreadsheet
      client = CardflasherClient(user_config)
      card = Card().Create(self.request.get('front'), self.request.get('back'), client)
      self.redirect('/list')
    except gdata.service.RequestError, request_error:
      user_config.auth_token_valid = False;
      user_config.put()
      self.redirect('/config')


class DeleteCard(BaseHandler):
  def post(self):
    # Check that user is properly configured
    user_config = self.getUserConfig()
    if user_config == None:
      return
    try:
      # get the card data from the spreadsheet using gdata
      client = CardflasherClient(user_config)
      card = Card().initFromEditUrl(self.request.get('edit_url'), client)
      card.Delete()
      self.redirect('/list')
    except gdata.service.RequestError, request_error:
      user_config.auth_token_valid = False;
      user_config.put()
      self.redirect('/config')


class ConfigureUser(BaseHandler):
  def get(self):
    # Check that user is properly configured
    user_config = self.getUserConfig()
    if user_config == None:
      return
    # show the config page
    template_dict = {
        'name'           : users.get_current_user().email(),
        'logout_url'     : users.create_logout_url('/'),
        'isDefault'      : user_config.auth_token == 'default',
        'authTokenValid' : user_config.auth_token_valid,
        }
    path = os.path.join(os.path.dirname(__file__), 'templates/config.html');
    self.response.out.write(template.render(path, template_dict))


class SelectSpreadsheet(BaseHandler):
  def post(self):
    # Check that user is properly configured
    user_config = self.getUserConfig()
    if user_config == None:
      return
    if self.request.get('select') == 'default':
      user_config.auth_token = 'default'
      user_config.put();
      self.redirect('/config')
      return
    # Create auth-sub redirect
    #######################################
    #HOST = "localhost:8080"
    HOST = "cardflasher.appspot.com"
    #######################################
    next_url = "http://%s/postAuthSub" % (HOST)
    scope = "http://spreadsheets.google.com/feeds"
    secure = False
    session = True
    client = gdata.service.GDataService()
    authsub_url = client.GenerateAuthSubURL(next_url, scope, secure, session);
    self.redirect(authsub_url)


class PostAuthSub(BaseHandler):
  def get(self):
    user_config = self.getUserConfig()
    if user_config == None:
      return
    # upgrade session token to authsub token
    session_token = self.request.get('token')
    client = gdata.service.GDataService()
    client.SetAuthSubToken(session_token)
    client.UpgradeToSessionToken()
    # store the AuthSub token in the user's config
    user_config.auth_token = client.GetAuthSubToken()
    user_config.auth_token_valid = True
    # SHURTCUT: use a hard coded spreadsheet (a picker would be better)
    user_config.spreadsheet_key = 'pFTNU5W-U0barlRKCdCAaKg'
    user_config.worksheet_key = 'od6'
    user_config.put()
    self.redirect('/config')


class Logout(BaseHandler): 
  def get(self):
    user_config = self.getUserConfig()
    if user_config == None:
      return
    self.redirect(users.create_logout_url('/'))


class UnknownUrl(BaseHandler):
  def get(self):
    self.redirect('/')


def main():
  application = webapp.WSGIApplication(
    [
      ('/', RootHandler),
      ('/question', ShowQuestion),
      ('/answer', ShowAnswer),
      ('/update', UpdateCard),
      ('/list', ListCards),
      ('/insert', InsertCard),
      ('/delete', DeleteCard),
      ('/config', ConfigureUser),
      ('/select', SelectSpreadsheet),
      ('/postAuthSub', PostAuthSub),
      ('/logout', Logout), 
      (r'/.*', UnknownUrl),
    ],
    debug=True)
  run_wsgi_app(application)


if __name__ == '__main__':
  main()
